package com.ponhu.ea2.stockcenter.provider.service.biz.stockwarehouse;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ponhu.ea2.stockcenter.dto.WMSEventInfoDTO;
import com.ponhu.ea2.stockcenter.dto.WMSInventoryInfo;
import com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum;
import com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum;
import com.ponhu.ea2.stockcenter.provider.entity.*;
import com.ponhu.ea2.stockcenter.provider.service.entity.*;
import com.ponhu.ea2.stockcenter.provider.support.enums.StockChangeInnerEventEnum;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.support.vo.InventoryInfoVO;
import com.ponhu.ea2.stockcenter.provider.utils.CommonUtils;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum.*;
import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.*;
import static com.ponhu.ea2.stockcenter.provider.support.enums.StockChangeInnerEventEnum.*;

@Slf4j
@Component
public class AbsAssistHandler {

    @Autowired
    private FastThreadLocal<String> traceIdLocal;
    @Autowired // 当前请求是否存在销售侧库存占用(销售订单占用或采退单占用)
    protected FastThreadLocal<Boolean> isSaleOccupyLocal;
    @Autowired // 获取请求参数Map(key: 货品编码，value: 仓库变动数量)
    private FastThreadLocal<Map<String, Integer>> inventoryLocal;
    @Autowired // 当前请求类型对销售侧库存影响的类型枚举
    private FastThreadLocal<StockChangeInnerEventEnum> saleEventLocal;
    @Autowired // 当前请求类型对销售侧库存影响的类型枚举
    protected FastThreadLocal<List<StockWarehouseChange>> whChangesLocal;

    @Autowired
    protected AbsUtilHandler absUtilHandler;
    @Autowired
    protected StockInventoryRealService stockInventoryRealService;
    @Autowired
    protected StockInventoryStatisticsService stockInventoryStatisticsService;
    @Autowired
    protected StockWarehouseChangeService stockWarehouseChangeService;
    @Autowired
    protected StockInventoryDocOccupyService stockInventoryDocOccupyService;
    @Autowired
    protected StockInventoryWarehouseTraceService stockInventoryWarehouseTraceService;

    /**
     * 补充请求参数前的预校验
     */
    protected void preInitCheck(WMSEventInfoDTO eventInfoDTO,
                                List<ExchangeActionTypeEnum> typeEnumList) {

        List<StockWarehouseChange> changes = stockWarehouseChangeService
                .getStockWarehouseChanges(eventInfoDTO.getTransferNo(), typeEnumList);
        whChangesLocal.set(changes);

        // 调拨操作前校验调拨申请是否已提交
        boolean anyMatchApply = changes.stream().anyMatch(change ->
                TRANSFER_APPLY.getCode().equals(change.getActionType()));
        if (BooleanUtils.isNotTrue(anyMatchApply)) { // 调拨出库/入库/取消前必须存在调拨申请
            if (TRANSFER_APPLY == eventInfoDTO.getActionType()) return;

            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900107);
        } else if (TRANSFER_APPLY == eventInfoDTO.getActionType()) {
            // 相同调拨单号重复提交调拨申请
            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900110);
        }

        // 调拨操作前校验调拨申请是否已取消
        boolean anyMatchApplyCancel = changes.stream().anyMatch(change ->
                TRANSFER_APPLY_CANCEL.getCode().equals(change.getActionType()));
        if (BooleanUtils.isTrue(anyMatchApplyCancel)) { // 调拨申请已取消
            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900109);
        }

        // 如果当前请求时取消调拨申请请求，则校验其要取消的调拨申请是否已经执行即已经执行了调拨出库或调拨入库
        if (TRANSFER_APPLY_CANCEL == eventInfoDTO.getActionType()) {
            boolean applyExecuted = changes.stream().anyMatch(change ->
                    TRANSFER_IN.getCode().equals(change.getActionType()) ||
                            TRANSFER_OUT.getCode().equals(change.getActionType()));
            if (BooleanUtils.isTrue(applyExecuted)) { // 调拨申请已执行调拨出库或入库
                throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900112);
            }
        }

        // 调拨出库请求可以分批执行，多次调拨出库的总数量不可大于调拨申请的数量
        // 调拨入库请求可以分批执行，多次调拨入库的总数量不可大于调拨申请的数量
        List<ExchangeActionTypeEnum> applyExecuteTypes = Arrays.asList(
                TRANSFER_OUT, TRANSFER_IN);
        if (applyExecuteTypes.contains(eventInfoDTO.getActionType())) {
            absUtilHandler.checkApplyExecuted(eventInfoDTO);
        }
    }

    /**
     * 补充调拨申请相关请求参数：调拨出库、调拨入库、取消调拨申请
     */
    protected void dtoSupply(WMSEventInfoDTO eventInfoDTO) {

        // 校验当前请求是否指定了调拨货品信息
        if (CollectionUtils.isEmpty(eventInfoDTO.getInventoryInfoList())) {
            List<WMSInventoryInfo> inventoryInfos = whChangesLocal.get().stream()
                    .filter(change -> TRANSFER_APPLY.getCode().equals(change.getActionType()))
                    .map(change -> {
                        WMSInventoryInfo wmsInventoryInfo = new WMSInventoryInfo()
                                .setInventoryCode(change.getInventoryCode())
                                .setInventoryQuantity(change.getUpdatedNum()); // 此处保持原正负值
                        if (eventInfoDTO.getActionType() == TRANSFER_OUT) {
                            wmsInventoryInfo.setInventoryQuantity(-change.getUpdatedNum());
                        }
                        return wmsInventoryInfo;
                    }).collect(Collectors.toList());
            eventInfoDTO.setInventoryInfoList(inventoryInfos);
        }

        // 根据调拨申请记录补充请求参数
        StockWarehouseChange warehouseChange = whChangesLocal.get().stream()
                .filter(change -> TRANSFER_APPLY.getCode().equals(change.getActionType()))
                .findAny().get();
        eventInfoDTO.setOutWarehouseCode(warehouseChange.getOutWarehouseCode())
                .setOutWarehouseIsSale(warehouseChange.getOutWarehouseSale())
                .setInWarehouseCode(warehouseChange.getInWarehouseCode())
                .setInWarehouseIsSale(warehouseChange.getInWarehouseSale());
    }

    /**
     * 根据当前请求类型确定对销售侧库存影响的类型枚举
     */
    protected void saleEventInit(WMSEventInfoDTO eventInfoDTO, Boolean operateWarehouse) {

        Boolean inWarehouseIsSale = eventInfoDTO.getInWarehouseIsSale();
        Boolean outWarehouseIsSale = eventInfoDTO.getOutWarehouseIsSale();
        // 校验当前仓库库存变动对销售库存是否影响
        boolean salesStock = operateWarehouse ? inWarehouseIsSale : outWarehouseIsSale;

        // 调拨申请、调拨出库、调拨入库、取消调拨申请，这四种变动类型还需特别校验
        if (ObjectUtils.allNotNull(inWarehouseIsSale, outWarehouseIsSale)) {
            // 可售仓(ture) -> 可售仓(true)   |   不可售仓(false) -> 不可售仓(false)
            if (!(inWarehouseIsSale ^ outWarehouseIsSale)) {
                salesStock = false; // 四种类型里的这两种情况对销售库存无影响
            }
        }

        // 当前仓库库存变动对销售库存无影响则返回
        if (BooleanUtils.isNotTrue(salesStock) &&
                !SALES_OUT.equals(eventInfoDTO.getActionType())) return;

        // 设置当前请求所属的销售仓库变动动作类型
        saleEventSwitch(eventInfoDTO);
    }

    /**
     * 设置当前请求所属的销售仓库变动动作类型
     */
    protected void saleEventSwitch(WMSEventInfoDTO eventInfoDTO) {

        StockChangeInnerEventEnum stockChangeInnerEventEnum = null;

        switch (eventInfoDTO.getActionType()) {

            case SALES_RETURN_CANCLE_OUT: // 取消销退入库(出库动作)-出可售仓
                stockChangeInnerEventEnum = CANCEL_SALES_RETURN_IN_SALES_WAREHOUSE;
                break;

            case PURCHASE_CANCEL_OUT: // 取消采购入库(出库动作)-出可售仓
                stockChangeInnerEventEnum = CANCEL_PURCHASE_IN_SALES_WAREHOUSE;
                break;

            case OTHER_OUT: // 其他出库-出可售仓
                stockChangeInnerEventEnum = OTHER_OUT_SALES_WAREHOUSE;
                break;

            case TRANSFER_PLAN_OUT: // 调拨计划出库-出可售仓，复用 OTHER_OUT
                stockChangeInnerEventEnum = OTHER_OUT_SALES_WAREHOUSE;
                break;

            case CHECK_LOSS_OUT: // 盘亏出库-出可售仓
                stockChangeInnerEventEnum = CHECK_LOSS_OUT_OF_SALES_WAREHOUSE;
                break;

            case PURCHASE_RETURN_OUT: // 采退出库-出可售仓
                // 根据当前请求操作是否是从可售仓到不可售仓来决定对销售侧库存影响动态类型
                // 根据当前请求的关联单据号查询已下单已占用且未调拨申请未出可售仓
                stockChangeInnerEventEnum = absUtilHandler.checkDocOccupy(eventInfoDTO,
                        docNo -> stockInventoryDocOccupyService
                                .getPlacedAndOccupyAndNoTransferNoOutDocRecord(docNo),
                        PURCHASE_RETURN_OUT_OF_SALES_WAREHOUSE_OCCUPY,
                        PURCHASE_RETURN_OUT_OF_SALES_WAREHOUSE);
                break;

            case SALES_OUT: // 销售出库
                // 根据当前请求操作是否是从可售仓到不可售仓来决定对销售侧库存影响动态类型
                // 根据当前请求的关联单据号查询已下单已占用且未调拨申请未出可售仓
                if (eventInfoDTO.getOutWarehouseIsSale()) { // 出可售仓
                    stockChangeInnerEventEnum = absUtilHandler.checkDocOccupy(eventInfoDTO,
                            docNo -> stockInventoryDocOccupyService
                                    .getPlacedAndOccupyAndNoTransferNoOutDocRecord(docNo),
                            SALES_OUT_OF_SALES_WAREHOUSE_OCCUPY,
                            STOCK_WAREHOUSE_EXCEPTION_900113);
                } else { // 出不可售仓
                    ResponseCodeEnum nonExistExceptionCode = null;
                    stockChangeInnerEventEnum = absUtilHandler.checkDocOccupy(eventInfoDTO,
                            docNo -> stockInventoryDocOccupyService.getDocOccupyRecord(docNo),
                            SALES_OUT_OF_UNSALES_WAREHOUSE_OCCUPY, nonExistExceptionCode);
                }
                break;

            case OTHER_IN: // 其他入库-入可售仓
                stockChangeInnerEventEnum = OTHER_IN_SALES_WAREHOUSE;
                break;

            case TRANSFER_PLAN_IN: // 调拨计划入库-入可售仓，复用 OTHER_IN
                stockChangeInnerEventEnum = OTHER_IN_SALES_WAREHOUSE;
                break;

            case PURCHASE_IN: // 采购入库-入可售仓
                stockChangeInnerEventEnum = PURCHASE_IN_SALES_WAREHOUSE;
                break;

            case CHECK_PROFIT_IN: // 盘盈入库-入可售仓
                stockChangeInnerEventEnum = CHECK_PROFIT_IN_SALES_WAREHOUSE;
                break;

            case SALES_RETURN_IN: // 销退入库-入可售仓
                stockChangeInnerEventEnum = SALES_RETURN_IN_SALES_WAREHOUSE;
                break;

            case TRANSFER_IN: // 调拨入库-入可售仓
                stockChangeInnerEventEnum = TRANSFER_IN_SALES_WAREHOUSE;
                break;

            case TRANSFER_APPLY: // 调拨申请-出可售仓
                // 根据当前请求操作是否是从可售仓到不可售仓来决定对销售侧库存影响动态类型
                stockChangeInnerEventEnum = absUtilHandler.checkDocOccupy(eventInfoDTO,
                        docNo -> stockInventoryDocOccupyService
                                .getPlacedAndOccupyAndNoTransferNoOutDocRecord(docNo),
                        TRANSFER_APPLY_OCCUPY, TRANSFER_APPLY_NO_OCCUPY);
                break;

            case TRANSFER_APPLY_CANCEL: // 取消调拨申请-对应调拨申请出可售仓
                // 根据当前请求操作是否是从可售仓到不可售仓来决定对销售侧库存影响动态类型
                // 根据当前请求的关联单据号查询已下单为占用且已调拨申请未出可售仓
                stockChangeInnerEventEnum = absUtilHandler.checkDocOccupy(eventInfoDTO,
                        docNo -> stockInventoryDocOccupyService
                                .getPlacedAndNoOccupyAndTransferAppliedAndNoOutedDocRecord(docNo),
                        CANCEL_TRANSFER_APPLY_AND_DOC_NO_CANCELED,
                        CANCEL_TRANSFER_APPLY_AND_NO_DOC_NO_AND_YES_TO_NO);
                break;
        }

        // 设置当前请求所属的销售仓库变动动作类型
        saleEventLocal.set(stockChangeInnerEventEnum);

        log.info("saleEventSwitch ===>> traceId[{}] ===>> eventInfoDTO={}，currentThread={}，saleEventLocal={}",
                traceIdLocal.get(), eventInfoDTO, Thread.currentThread().

                        getName(), saleEventLocal.

                        get());
    }

    /**
     * 可售仓出库库存校验 - 源
     */
    protected void commonSaleOutStockCheck_old(WMSEventInfoDTO eventInfoDTO, Boolean operateWarehouse) {

        // 获取对应货品销售占用或可卖库存集合并转MAP
        Map<String, Integer> freeOrOccupyMap = absUtilHandler.getFreeOrOccupyMap(eventInfoDTO);
        // 获取和当前请求互斥的调用链路货品库存占用情况
        Map<String, Integer> tracesToMap = absUtilHandler.getTracesMap(eventInfoDTO, operateWarehouse);

        // 有业务单号(链路单号)：实物可用库存 >= 调拨库存 && 销售占用 >= 调拨库存
        // 无业务单号(链路单号)：实物可用库存 - 全部链路占用 >= 调拨库存 &&
        //                                       销售占用(货品总可售仓库数 - 货品总销售占用) >= 调拨库存
        StringBuffer msg = new StringBuffer();
        absUtilHandler.whStockAllMacth(stock -> {
            String inventoryCode = stock.getInventoryCode(); // 货品编码
            Integer inventoryQuantity = inventoryLocal.get().get(inventoryCode); // 调拨库存
            Integer availableNum = stock.getAvailableStockNum(); // 实物可用库存
            Integer freeOrOccupy = freeOrOccupyMap.get(inventoryCode); // 销售占用 或 可卖库存
            Integer traceOccupy = tracesToMap.getOrDefault(inventoryCode, 0); // 全部链路占用(或排除当前)
            String freeOrOccupyType = isSaleOccupyLocal.get() ? "销售占用" : "可卖库存";

            boolean macth;
            if (CommonUtils.isEmpty(eventInfoDTO.getDocNo()) || traceOccupy == 0) { // 无业务单号(链路单号) 或 有业务单号无链路占用
                macth = availableNum - traceOccupy >= inventoryQuantity && freeOrOccupy >= inventoryQuantity;
                if (BooleanUtils.isNotTrue(macth))
                    msg.append(String.format(" 货品编码-%s：{ 实物可用库存(%s) - 全部链路占用(%s) >= 调拨库存(%s) && %s(%s) >= 调拨库存(%s) }校验失败；",
                            inventoryCode, availableNum, traceOccupy, inventoryQuantity, freeOrOccupyType, freeOrOccupy, inventoryQuantity));
            } else { // 有业务单号(链路单号) 且 业务单号有链路占用
                macth = availableNum - traceOccupy >= inventoryQuantity && freeOrOccupy >= inventoryQuantity;
                if (BooleanUtils.isNotTrue(macth))
                    msg.append(String.format(" 货品编码-%s：{ 实物可用库存(%s) - 其它链路占用(%s) >= 调拨库存(%s) && %s(%s) >= 调拨库存(%s) }校验失败；",
                            inventoryCode, availableNum, traceOccupy, inventoryQuantity, freeOrOccupyType, freeOrOccupy, inventoryQuantity));
            }
            return macth;
        }, msg);
    }

    /**
     * 可售仓出库库存校验 - 新
     */
    protected void commonSaleOutStockCheck(WMSEventInfoDTO eventInfoDTO, Boolean operateWarehouse) {

        StringBuffer msg = new StringBuffer();
        absUtilHandler.whStockAllMacth(stock -> {
            String inventoryCode = stock.getInventoryCode(); // 货品编码
            Integer inventoryQuantity = inventoryLocal.get().get(inventoryCode); // 调拨库存
            Integer availableNum = stock.getAvailableStockNum(); // 实物可用库存

            boolean macth = availableNum >= inventoryQuantity;
            if (BooleanUtils.isNotTrue(macth))
                msg.append(String.format(" 货品编码-%s：{ 实物可用库存(%s) >= 调拨库存(%s) }校验失败；",
                        inventoryCode, availableNum, inventoryQuantity));
            return macth;
        }, msg);
    }

    /**
     * 不可售仓出库库存校验 - 源
     */
    protected void commonNoSaleOutStockCheck_old(WMSEventInfoDTO eventInfoDTO, Boolean operateWarehouse) {

        // 获取和当前请求互斥的调用链路货品库存占用情况
        Map<String, Integer> tracesToMap = absUtilHandler.getTracesMap(eventInfoDTO, operateWarehouse);

        // 有业务单号(链路单号)：实物可用库存 - 全部链路占用 >= 调拨库存
        // 无业务单号(链路单号)：实物可用库存 >= 调拨库存
        StringBuffer msg = new StringBuffer();
        absUtilHandler.whStockAllMacth(stock -> {
            Integer inventoryQuantity = inventoryLocal.get().get(stock.getInventoryCode()); //调拨库存
            Integer traceOccupy = tracesToMap.getOrDefault(stock.getInventoryCode(), 0); // 全部链路占用(或排除当前)

            boolean macth;
            if (CommonUtils.isEmpty(eventInfoDTO.getDocNo()) || traceOccupy == 0) { // 无业务单号(链路单号) 或 有业务单号无链路占用
                macth = stock.getAvailableStockNum() - traceOccupy >= inventoryQuantity;
                if (BooleanUtils.isNotTrue(macth))
                    msg.append(String.format(" 货品编码-%s：{ 实物可用库存(%s) - 全部链路占用(%s) >= 调拨库存(%s) }校验失败；",
                            stock.getInventoryCode(), stock.getAvailableStockNum(), traceOccupy, inventoryQuantity));
            } else { // 有业务单号(链路单号) 且 业务单号有链路占用
                macth = stock.getAvailableStockNum() - traceOccupy >= inventoryQuantity;
                if (BooleanUtils.isNotTrue(macth))
                    msg.append(String.format(" 货品编码-%s：{ 实物可用库存(%s) - 其它链路占用(%s) >= 调拨库存(%s) }校验失败；",
                            stock.getInventoryCode(), stock.getAvailableStockNum(), traceOccupy, inventoryQuantity));
            }
            return macth;
        }, msg);
    }

    /**
     * 不可售仓出库库存校验 - 新
     */
    protected void commonNoSaleOutStockCheck(WMSEventInfoDTO eventInfoDTO, Boolean operateWarehouse) {

        StringBuffer msg = new StringBuffer();
        absUtilHandler.whStockAllMacth(stock -> {
            Integer inventoryQuantity = inventoryLocal.get().get(stock.getInventoryCode()); //调拨库存

            boolean macth = stock.getAvailableStockNum() >= inventoryQuantity;
            if (BooleanUtils.isNotTrue(macth))
                msg.append(String.format(" 货品编码-%s：{ 实物可用库存(%s) - >= 调拨库存(%s) }校验失败；",
                        stock.getInventoryCode(), stock.getAvailableStockNum(), inventoryQuantity));
            return macth;
        }, msg);
    }

    /**
     * 记录货品出入库的开始时间和结束时间
     */
    protected void whInOutRecord(WMSEventInfoDTO eventInfoDTO) {
        try {
            LocalDateTime now = LocalDateTime.now();
            String formatNow = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            List<String> inventoryCodes = eventInfoDTO.getInventoryInfoList().stream()
                    .map(WMSInventoryInfo::getInventoryCode).collect(Collectors.toList());

            List<ExchangeActionTypeEnum> inEnumList = Arrays.asList(
                    ExchangeActionTypeEnum.CHECK_PROFIT_IN,
                    ExchangeActionTypeEnum.PURCHASE_IN);
            if (inEnumList.contains(eventInfoDTO.getActionType())) {
                Map<String, StockInventoryReal> stockMap = stockInventoryRealService
                        .getRealInventoryStockListIfNullCreate(inventoryCodes).stream()
                        .collect(Collectors.toMap(StockInventoryReal::getInventoryCode, Function.identity()));

                List<StockInventoryStatistics> statisticsList = inventoryCodes.stream().map(inventoryCode ->
                        new StockInventoryStatistics()
                                .setGoodsId(stockMap.get(inventoryCode).getGoodsId())
                                .setInventoryId(stockMap.get(inventoryCode).getInventoryId())
                                .setInventoryCode(inventoryCode)
                                .setFirstWarehouseTime(now)
                                .setRemark(formatNow + " 记录开始时间 " + eventInfoDTO.getActionType().getCode()
                                        + "-" + eventInfoDTO.getActionType().getName())
                                .setCreateTime(LocalDateTime.now())).collect(Collectors.toList());
                try {
                    stockInventoryStatisticsService.saveBatch(statisticsList);
                } catch (Exception e) {
                    // 忽略唯一键约束异常
                }
            }

            List<ExchangeActionTypeEnum> outEnumList = Arrays.asList(
                    ExchangeActionTypeEnum.SALES_RETURN_CANCLE_OUT,
                    ExchangeActionTypeEnum.PURCHASE_CANCEL_OUT,
                    ExchangeActionTypeEnum.PURCHASE_RETURN_OUT,
                    ExchangeActionTypeEnum.CHECK_LOSS_OUT,
                    ExchangeActionTypeEnum.SALES_OUT);
            if (outEnumList.contains(eventInfoDTO.getActionType())) {
                LambdaQueryWrapper<StockInventoryStatistics> queryWrapper = Wrappers
                        .lambdaQuery(StockInventoryStatistics.class)
                        .in(StockInventoryStatistics::getInventoryCode, inventoryCodes)
                        .isNull(StockInventoryStatistics::getLastWarehouseTime);
                List<StockInventoryStatistics> statisticsList = stockInventoryStatisticsService.list(queryWrapper).stream()
                        .map(sis -> sis.setLastWarehouseTime(now).setUpdateTime(now)
                                .setRemark(sis.getRemark() + "\n | " + formatNow + " 记录结束时间 "
                                        + eventInfoDTO.getActionType().getCode() + "-"
                                        + eventInfoDTO.getActionType().getName()))
                        .collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(statisticsList))
                    stockInventoryStatisticsService.updateBatchById(statisticsList);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("入库或出库事件时记录货品入库时间或出库时间异常 ===>> Error={}", e.getMessage());
        }
    }

}
